package de.dnb.gnd.utils;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.dnb.basics.applicationComponents.Pair;
import de.dnb.basics.applicationComponents.RangeCheckUtils;
import de.dnb.basics.applicationComponents.StringUtils;
import de.dnb.basics.filtering.Between;

/**
 * Utilities zu den DDC-Sachgruppen.
 * 
 * @author baumann
 *
 */
public final class DHSUtils {

    private DHSUtils() {

    }

    public static enum REFERATE {
        SOCIAL_SCIENCES("Sozialwissenschaften"), HUMANITIES(
            "Geisteswissenschaften"), STM("STM");

        public String name;

        REFERATE(String name) {
            this.name = name;
        }
    }

    private static
        TreeMap<Between<String>, Pair<String, String>>
        allIntervalls() {
        fillIntervals();
        return interval2sg;
    }

    /**
     * 
     * Gibt zur ddc ein Paar(DHS, Beschreibung).
     * 
     * @param ddc   nicht null
     * @return      Sachgruppe oder null
     * @throws      IllegalArgumentException, wenn nicht dem DDC-Muster
     *              entsprechend
     */
    public static Pair<String, String> getSG(final String ddc) {
        RangeCheckUtils.assertReferenceParamNotNull("ddc", ddc);
        Matcher matcher = singlePattern.matcher(ddc);
        if (!matcher.matches())
            throw new IllegalArgumentException("ddc " + ddc
                + " entspricht nicht dem Muster ###.## ...");
        fillIntervals();
        TreeMap<Between<String>, Pair<String, String>> ints2SG =
            allIntervalls();
        Between<String> dummy = new Between<String>(ddc, end);
        Between<String> current = ints2SG.floorKey(dummy);
        while (current != null) {
            if (current.accept(ddc))
                return ints2SG.get(current);
            else
                current = ints2SG.lowerKey(current);
        }
        return null;
    }

    /**
     * Gibt zur Sachgruppe das zugehörige Referat.
     * 
     * @param dhs   beliebig
     * @return      Referat oder null
     */
    public static REFERATE getReferat(final String dhs) {
        if (humanitiesDDC().contains(dhs))
            return REFERATE.HUMANITIES;
        if (socialDDC().contains(dhs))
            return REFERATE.SOCIAL_SCIENCES;
        if (stmDDC().contains(dhs))
            return REFERATE.STM;
        return null;
    }

    public static void main(String[] args) {
        String s = StringUtils.readConsole();
        while (true) {
            System.out.println(getSG(s));
            s = StringUtils.readConsole();
        }

    }

    /**
     * 
     * @return Alle Sachgruppen der Geisteswissenschaften, nicht veränderbar
     */
    public static Collection<String> humanitiesDDC() {
        if (DHSUtils.HUMANITIES_SG == null) {
            DHSUtils.HUMANITIES_SG =
                Arrays.asList("000", "010", "020", "030", "050", "060", "070",
                    "080", "090",

                    "100", "130",

                    "200", "220", "230", "290",

                    "390",

                    "400", "420", "430", "439", "440", "450", "460", "470",
                    "480", "490", "491.8",

                    "700", "710", "720", "730", "740", "741.5", "750", "760",
                    "770", "780", "790", "791", "792", "793", "796",

                    "800", "810", "820", "830", "839", "840", "850", "860",
                    "870", "880", "890", "891.8",

                    "900", "920", "930", "940", "943", "950", "960", "970",
                    "980", "990",

                    "B", "K");
        }
        return DHSUtils.HUMANITIES_SG;
    }

    /**
     * 
     * @return Alle Sachgruppen der Sozialwissenschaften, nicht veränderbar
     */
    public static Collection<String> socialDDC() {
        if (DHSUtils.SOCIAL_SG == null) {
            DHSUtils.SOCIAL_SG =
                Arrays.asList("150",

                "300", "310", "320", "330", "333.7", "340", "350", "355",
                    "360", "370", "380",

                    "640", "650",

                    "910", "914.3",

                    "S");
        }
        return DHSUtils.SOCIAL_SG;
    }

    /**
     * 
     * @return 		Alle Sachgruppen der STM-Fächer (Mathematik,
     * 			 	Naturwissenschaften, Medizin, Ingenieurwissenschaften), 
     *  			nicht veränderbar
     */
    public static Collection<String> stmDDC() {
        if (DHSUtils.STM_SG == null) {
            DHSUtils.STM_SG =
                Arrays.asList("004",

                "500", "510", "520", "530", "540", "550", "560", "570", "580",
                    "590",

                    "600", "610", "620", "621.3", "624", "630", "660", "670",
                    "690"

                );
        }
        return DHSUtils.STM_SG;
    }

    /**
     * 
     * @return Sachgruppen ohne"B", also alle möglichen Haupsachgruppen
     */
    public static Collection<String> allDHS() {
        Collection<String> dhs = new LinkedHashSet<>(alleDDCSachgruppen());
        dhs.remove("B");
        return dhs;
    }

    public static Collection<String> alleDDCSachgruppen() {
        if (DDC_SG == null) {
            DDC_SG =
                Arrays.asList("000", "004", "010", "020", "030", "050", "060",
                    "070", "080", "090",

                    "100", "130", "150",

                    "200", "220", "230", "290",

                    "300", "310", "320", "330", "333.7", "340", "350", "355",
                    "360", "370", "380", "390",

                    "400", "420", "430", "439", "440", "450", "460", "470",
                    "480", "490", "491.8",

                    "500", "510", "520", "530", "540", "550", "560", "570",
                    "580", "590",

                    "600", "610", "620", "621.3", "624", "630", "640", "650",
                    "660", "670", "690",

                    "700", "710", "720", "730", "740", "741.5", "750", "760",
                    "770", "780", "790", "791", "792", "793", "796",

                    "800", "810", "820", "830", "839", "840", "850", "860",
                    "870", "880", "890", "891.8",

                    "900", "910", "914.3", "920", "930", "940", "943", "950",
                    "960", "970", "980", "990",

                    "B", "K", "S");
        }
        return DDC_SG;
    }

    /**
     * 
     * @param ddcSG	nicht null
     * @return	Info (Benennung des Fachgebietes, enthaltene DDC-Klassen)
     * 			zur DDC-SG.
     */
    public static Pair<String, String> getInfo(String ddcSG) {
        if (sg2info == null) {
            sg2info = new HashMap<>(120);

            sg2info.put("000", new Pair<String, String>(
                "Allgemeines, Wissenschaft", "000-003"));
            sg2info.put("004",
                new Pair<String, String>("Informatik", "004-006"));
            sg2info
                .put("010", new Pair<String, String>("Bibliografien", "010"));
            sg2info.put("020", new Pair<String, String>(
                "Bibliotheks- und Informationswissenschaft", "020"));
            sg2info
                .put("030", new Pair<String, String>("Enzyklopädien", "030"));
            sg2info.put("050", new Pair<String, String>(
                "Zeitschriften, fortlaufende Sammelwerke", "050"));
            sg2info.put("060", new Pair<String, String>(
                "Organisationen, Museumswissenschaft", "060"));
            sg2info.put("070", new Pair<String, String>(
                "Nachrichtenmedien, Journalismus, Verlagswesen", "070"));
            sg2info.put("080", new Pair<String, String>(
                "Allgemeine Sammelwerke", "080"));
            sg2info.put("090", new Pair<String, String>(
                "Handschriften, seltene Bücher", "090"));

            sg2info.put("100", new Pair<String, String>("Philosophie",
                "100-120, 140, 160-190"));
            sg2info.put("130", new Pair<String, String>(
                "Parapsychologie, Okkultismus", "130"));
            sg2info.put("150", new Pair<String, String>("Psychologie", "150"));
            sg2info.put("", new Pair<String, String>("", ""));
            sg2info.put("200", new Pair<String, String>(
                "Religion, Religionsphilosophie", "200, 210"));
            sg2info.put("220", new Pair<String, String>("Bibel", "220"));
            sg2info.put("230", new Pair<String, String>(
                "Theologie, Christentum", "230-280"));
            sg2info.put("290", new Pair<String, String>("Andere Religionen",
                "290"));

            sg2info.put("300", new Pair<String, String>(
                "Sozialwissenschaften, Soziologie, Anthropologie", "300"));
            sg2info.put("310", new Pair<String, String>(
                "Allgemeine Statistiken", "310"));
            sg2info.put("320", new Pair<String, String>("Politik", "320"));
            sg2info.put("330", new Pair<String, String>("Wirtschaft", "330"));
            sg2info.put("333.7", new Pair<String, String>(
                "Natürliche Ressourcen, Energie und Umwelt", "333.7-333.9"));
            sg2info.put("340", new Pair<String, String>("Recht", "340"));
            sg2info.put("350", new Pair<String, String>(
                "Öffentliche Verwaltung", "350-354"));
            sg2info.put("355", new Pair<String, String>("Militär", "355-359"));
            sg2info.put("360", new Pair<String, String>(
                "Soziale Probleme, Sozialdienste, Versicherungen", "360"));
            sg2info.put("370", new Pair<String, String>(
                "Erziehung, Schul- und Bildungswesen", "370"));
            sg2info.put("380", new Pair<String, String>(
                "Handel, Kommunikation, Verkehr", "380"));
            sg2info.put("390", new Pair<String, String>(
                "Bräuche, Etikette, Folklore", "390"));

            sg2info.put("400", new Pair<String, String>("Sprache, Linguistik",
                "400, 410"));
            sg2info.put("420", new Pair<String, String>("Englisch", "420"));
            sg2info.put("430", new Pair<String, String>("Deutsch", "430"));
            sg2info.put("439", new Pair<String, String>(
                "Andere germanische Sprachen", "439"));
            sg2info.put("440", new Pair<String, String>(
                "Französisch, romanische Sprachen allgemein", "440"));
            sg2info.put("450", new Pair<String, String>(
                "Italienisch, Rumänisch, Rätoromanisch", "450"));
            sg2info.put("460", new Pair<String, String>(
                "Spanisch, Portugiesisch", "460"));
            sg2info.put("470", new Pair<String, String>("Latein", "470"));
            sg2info.put("480", new Pair<String, String>("Griechisch", "480"));
            sg2info.put("490", new Pair<String, String>("Andere Sprachen",
                "490"));
            sg2info.put("491.8", new Pair<String, String>("Slawische Sprachen",
                "491.7-491.8"));

            sg2info.put("500", new Pair<String, String>("Naturwissenschaften",
                "500"));
            sg2info.put("510", new Pair<String, String>("Mathematik", "510"));
            sg2info.put("520", new Pair<String, String>(
                "Astronomie, Kartografie", "520"));
            sg2info.put("530", new Pair<String, String>("Physik", "530"));
            sg2info.put("540", new Pair<String, String>("Chemie", "540"));
            sg2info.put("550", new Pair<String, String>("Geowissenschaften",
                "550"));
            sg2info
                .put("560", new Pair<String, String>("Paläontologie", "560"));
            sg2info.put("570", new Pair<String, String>(
                "Biowissenschaften, Biologie", "570"));
            sg2info.put("580", new Pair<String, String>("Pflanzen (Botanik)",
                "580"));
            sg2info.put("590", new Pair<String, String>("Tiere (Zoologie)",
                "590"));

            sg2info.put("600", new Pair<String, String>("Technik", "600"));
            sg2info.put("610", new Pair<String, String>("Medizin, Gesundheit",
                "610"));
            sg2info
                .put(
                    "620",
                    new Pair<String, String>(
                        "Ingenieurwissenschaften und Maschinenbau",
                        "620, 621 (außer 621.3 und 621.46), 623, 625.19, 625.2, 629 (außer 629.8)"));
            sg2info.put("621.3", new Pair<String, String>(
                "Elektrotechnik, Elektronik", "621.3, 621.46, 629.8"));
            sg2info.put("624", new Pair<String, String>(
                "Ingenieurbau und Umwelttechnik",
                "622, 624-628 (außer 625.19 und 625.2)"));
            sg2info.put("630", new Pair<String, String>(
                "Landwirtschaft, Veterinärmedizin", "630"));
            sg2info.put("640", new Pair<String, String>(
                "Hauswirtschaft und Familienleben", "640"));
            sg2info.put("650", new Pair<String, String>("Management", "650"));
            sg2info.put("660", new Pair<String, String>("Technische Chemie",
                "660"));
            sg2info.put("670", new Pair<String, String>(
                "Industrielle und handwerkliche Fertigung", "670, 680"));
            sg2info.put("690", new Pair<String, String>("Hausbau, Bauhandwerk",
                "690"));

            sg2info.put("700", new Pair<String, String>(
                "Künste, Bildende Kunst allgemein", "700"));
            sg2info.put("710", new Pair<String, String>(
                "Landschaftsgestaltung, Raumplanung", "710"));
            sg2info.put("720", new Pair<String, String>("Architektur", "720"));
            sg2info.put("730", new Pair<String, String>(
                "Plastik, Numismatik, Keramik, Metallkunst", "730"));
            sg2info.put("740", new Pair<String, String>(
                "Grafik, angewandte Kunst", "740"));
            sg2info.put("741.5", new Pair<String, String>(
                "Comics, Cartoons, Karikaturen", "741.5"));
            sg2info.put("750", new Pair<String, String>("Malerei", "750"));
            sg2info.put("760", new Pair<String, String>("Druckgrafik, Drucke",
                "760"));
            sg2info.put("770", new Pair<String, String>(
                "Fotografie, Video, Computerkunst", "770"));
            sg2info.put("780", new Pair<String, String>("Musik", "780"));
            sg2info.put("790", new Pair<String, String>(
                "Freizeitgestaltung, Darstellende Kunst", "790-790.2"));
            sg2info.put("791", new Pair<String, String>(
                "Öffentliche Darbietungen, Film, Rundfunk", "791"));
            sg2info
                .put("792", new Pair<String, String>("Theater, Tanz", "792"));
            sg2info.put("793", new Pair<String, String>("Spiel", "793-795"));
            sg2info.put("796", new Pair<String, String>("Sport", "796-799"));

            sg2info.put("800", new Pair<String, String>(
                "Literatur, Rhetorik, Literaturwissenschaft", "800"));
            sg2info.put("810", new Pair<String, String>(
                "Englische Literatur Amerikas", "810"));
            sg2info.put("820", new Pair<String, String>("Englische Literatur",
                "820"));
            sg2info.put("830", new Pair<String, String>("Deutsche Literatur",
                "830"));
            sg2info.put("839", new Pair<String, String>(
                "Literatur in anderen germanischen Sprachen", "839"));
            sg2info.put("840", new Pair<String, String>(
                "Französische Literatur", "840"));
            sg2info.put("850", new Pair<String, String>(
                "Italienische, rumänische, rätoromanische Literatur", "850"));
            sg2info.put("860", new Pair<String, String>(
                "Spanische und portugiesische Literatur", "860"));
            sg2info.put("870", new Pair<String, String>(
                "Lateinische Literatur", "870"));
            sg2info.put("880", new Pair<String, String>(
                "Griechische Literatur", "880"));
            sg2info.put("890", new Pair<String, String>(
                "Literatur in anderen Sprachen", "890"));
            sg2info.put("891.8", new Pair<String, String>(
                "Slawische Literatur", "891.7-891.8"));

            sg2info.put("900", new Pair<String, String>("Geschichte", "900"));
            sg2info.put("910", new Pair<String, String>("Geografie, Reisen",
                "910"));
            sg2info.put("914.3", new Pair<String, String>(
                "Geografie, Reisen (Deutschland)", "914.3-914.35"));
            sg2info.put("920", new Pair<String, String>(
                "Biografie, Genealogie, Heraldik", "920"));
            sg2info.put("930", new Pair<String, String>(
                "Alte Geschichte, Archäologie", "930"));
            sg2info.put("940", new Pair<String, String>("Geschichte Europas",
                "940"));
            sg2info.put("943", new Pair<String, String>(
                "Geschichte Deutschlands", "943-943.5"));
            sg2info.put("950", new Pair<String, String>("Geschichte Asiens",
                "950"));
            sg2info.put("960", new Pair<String, String>("Geschichte Afrikas",
                "960"));
            sg2info.put("970", new Pair<String, String>(
                "Geschichte Nordamerikas", "970"));
            sg2info.put("980", new Pair<String, String>(
                "Geschichte Südamerikas", "980"));
            sg2info.put("990", new Pair<String, String>(
                "Geschichte der übrigen Welt", "990"));

            sg2info.put("B", new Pair<String, String>("Belletristik", ""));
            sg2info.put("K", new Pair<String, String>(
                "Kinder- und Jugendliteratur", ""));
            sg2info.put("S", new Pair<String, String>("Schulbücher", ""));
        }
        return sg2info.get(ddcSG);
    }

    /**
     * Entfernt die Phrase, die durch "(außer .. )" die Ausnahmen kennzeichnet.
     * @param s
     * @return
     */
    private static String removeAusser(final String s) {
        String stripped = s.replaceAll(" \\(außer [^)]*\\)", "");
        return stripped;

    }

    private static String end = "z";
    private static String numberPat = "\\d\\d\\d(\\.\\d+)?";
    private static Pattern singlePattern = Pattern.compile(numberPat);
    private static String spanPat = "(" + numberPat + ")-(" + numberPat + ")";
    private static Pattern spanPattern = Pattern.compile(spanPat);

    /**
     * Macht 
     * 
     * a)   aus einer einzelnen Nummer ein Intervall, das mit der Nummer
     *      beginnt und mit dem nächsten Hunderter endet
     * b)   aus einem Bereich (###-###) das entsprechende Intervall
     * @param s
     * @return
     */
    private static Between<String> getDDCIntervall(final String s) {
        String low;
        String high;

        Matcher singeleMatcher = singlePattern.matcher(s);
        if (singeleMatcher.matches()) {
            low = s;
            high = makeUpperBound(s);
            return new Between<String>(low, high);
        }

        Matcher spanMatcher = spanPattern.matcher(s);
        if (spanMatcher.matches()) {
            low = spanMatcher.group(1);
            // 3, da in der Klammer noch Klammern stehen!
            high = makeUpperBound(spanMatcher.group(3));
            return new Between<String>(low, high);
        }
        return null;
    }

    /**
     * Macht eine obere Schranke durch Entfernen der letzten 0 und
     * Anhängen des Ende-Zeichens. Letzteres, um halboffene
     * Intervalle zu erzeugen.
     * 
     * @param s
     * @return
     */
    private static String makeUpperBound(final String s) {
        return s.replaceFirst("0$", "") + end;
    }

    /**
     * Macht aus der Sachgruppenbeschreibung eine Liste von Intervallen,
     * dien die SG beschreiben.
     * 
     * @param s
     * @return
     */
    private static Collection<Between<String>> getDDCIntervalls(final String s) {
        Collection<Between<String>> betweens = new LinkedList<>();
        String removed = removeAusser(s);
        String[] strings = removed.split(", ");
        for (String string : strings) {
            Between<String> between = getDDCIntervall(string);
            if (between != null)
                betweens.add(between);
        }
        return betweens;
    }

    /**
     * Füllt die Datenstruktur interval2sg mit Intervallen.
     */
    private static void fillIntervals() {
        if (interval2sg != null)
            return;
        interval2sg = new TreeMap<>();
        Collection<String> allDHS = allDHS();
        for (String dhs : allDHS) {
            Pair<String, String> info = getInfo(dhs);
            Pair<String, String> sgNamePair =
                new Pair<String, String>(dhs, info.first);
            String contained = info.second;
            Collection<Between<String>> intervals = getDDCIntervalls(contained);
            for (Between<String> interval : intervals) {
                interval2sg.put(interval, sgNamePair);
            }
        }
    }

    private static Collection<String> STM_SG = null;
    private static Collection<String> SOCIAL_SG = null;
    private static Collection<String> HUMANITIES_SG = null;
    private static Collection<String> DDC_SG = null;
    private static HashMap<String, Pair<String, String>> sg2info = null;
    private static TreeMap<Between<String>, Pair<String, String>> interval2sg;

}
